package url;

import java.io.*;
import java.net.*;
import util.Timer;

/**
 *
 * This code downlaoded from
 * https://code.google.com/p/nicenet/
 *
 *
 * This is a class to make life easier by having a URL class that has built into it
 * the handling of errors beyond those addressed in java.net URL/URI combo.
 *
 * <p>An address in NiceURL
 * has all the parts of an address in java.net.URL except for the fragment (# and everything after it - anchors). This is because that
 * part of the URL is not essential to finding its content, and we want resources to be unique by location, not
 * the anchoring "sub-locations" provided by the fragment information.</p>
 *
 *
 * @author KTK
 */
public class SimpleURL {



    /**
     * Time limit on HTTP request.
     */
    private static int TIMER_LIMIT = 30000;

    /**
     * Getter for HTTP request time limit
     */
    public static int getTimerLimit() {
        return TIMER_LIMIT;
    }

    /**
     * Setter for HTTP request time limit
     */
    public static void setTimerLimit(int limit) {
        TIMER_LIMIT = limit;
    }


    /**
     * This is what determines whether or not to save the contents, or make repeated http requests.
     */
    private boolean careful = true;
    /**
     * This is when we'd decided, for whatever reason, that this is a lost cause. Possible
     * reasons include discovering a non-text header, or encountering a timeout
     */
    private boolean lostCause = false;

    private boolean verbose;
    /**
     * Timer
     */
    private static Timer timer;

    /**
     * Underlying URL object
     */
    private URL url;

    /**
     * Actual contents of the page
     */
    private StringBuilder contents;

    public SimpleURL(String URL, boolean verbose) throws MalformedURLException{
        this.url = new URL(URL);
        this.verbose = verbose;
    }
    

    /**
     *
     * Retrieves, and returns in String format, the contents of the website corresponding to the specified
     * NiceURL instance.
     * @param u NiceURL to read from1
     * @return
     */
    public static StringBuilder getURLContents(SimpleURL u, boolean verbose) {
        StringBuilder sb = getURLContents(u.url, verbose);
        if(sb==null)
            u.lostCause = true;
        return sb;
    }

    /**
     *
     * Retrieves, and returns in String format, the contents of the website corresponding to the specified
     * URL instance.
     * @param u URL to read from
     * @return
     */
    public static StringBuilder getURLContents(URL u) {
        return getURLContents(u, false);
    }

    public static StringBuilder getURLContents(URL u, boolean verbose) {
        if (u == null) {
            return null;
        }
        try {
            long time = System.currentTimeMillis();
            URLConnection uconnect = u.openConnection();
            uconnect.setReadTimeout(30000);

            String header = uconnect.getContentType();
            if (header == null) {
                return null;
            }
            int end = (header.length() >= 4) ? 4 : header.length();
            header = header.substring(0, end);
            header = header.toLowerCase();
            if (!header.equals("text")) {
                if(verbose)
                    System.out.println("Returning NULL: skipping Non-text content type.");
                return null;
            }


            InputStream stream = (InputStream) uconnect.getContent();
            StringBuilder output = new StringBuilder();

            int letter = -1;

            if (timer != null) {
                timer.begin();
            }
            while ( (letter = stream.read()) != -1) {
                if (timer != null && !timer.ok()) {
                    timer.reset();
                    if(verbose)
                        System.out.println("Inputstream timeout - WARNING: this _should_ only happen when we have a non-text site.");
                    return null;
                }

                output.append((char) letter);
            }
            if(verbose)
                System.out.println("Read in "+(System.currentTimeMillis()-time));
            if (timer != null) {
                timer.reset();
            }
            stream.close();
            return output;

        } catch (Exception ex) {
            if(verbose)
                System.out.println("getURLContents(" + u + ")\t " + ex);
            return null;
        }

    }

    /**
     * Returns (when possible) the contents of the online resource this NiceURL refers to.
     * <i>Please Note:</i> This is an accessor method; once a website has been read once during the life of this instance,
     * it is not read again.
     * @return String of the contents
     */
    public StringBuilder getContents() {
        if (isLostCause()) {
            return null;
        }

        if (careful) {
            return getURLContents(this, verbose);
        }

        if (contents == null) {
            contents = getURLContents(this, verbose);
        }
        return contents;
    }

    public String getAddress() {
        return url.toExternalForm();
    }

    public URL getURL() {
        return url;
    }

    /**
     * Getter for directory flag
     * @return true iff address points to a directory
     */
    public boolean isDir() {
        return false;
    }

    /**
     * Checks if this URL has been attempted before; if it has, and has timed out,
     * it is deement a "lost cause."
     * @return
     */
    public boolean isLostCause() {
        return lostCause;
    }

    /**
     * A simplification so that we can use .equals on this object with a string
     * address. This is done by makign a new NiceURL instance. So that means
     * we are doing all this validation stuff over again. Be aware.
     * @param address
     * @return
     */
    public boolean equals(String address) {
        try {
            SimpleURL test = new SimpleURL(address, false);
            return this.equals(test);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     *
     * @param other
     * @return <code>true</code> iff the URL-containign fields of the two objects are themselves equal
     */
    @Override
    public boolean equals(Object other) {
        if (other.getClass() != this.getClass()) {
            return false;
        }

        return (this.getURL()).equals(((NiceURL) other).getURL());
    }

    /**
     * Hashes over the URL-ontaining field
     * @return hashcode
     */
    @Override
    public int hashCode() {
        int hash = 3;
        hash = 29 * hash + (this.url != null ? this.url.hashCode() : 0);
        return hash;
    }

    /**
     * Can change the contents quickly.
     * @param contents New content
     * @return wasblank Return <code>true</code> iff the existing contents field was blank. So <code>false</code> means
     * that we overwrote something - not the jurisdiction of this method, but should be dealth with appropriately!
     */
    protected boolean setContents(StringBuilder contents) {
        boolean out = true;
        if (this.contents != null) {
            out = false;
        }
        this.contents = contents;
        return out;
    }

    /**
     * Check what we are saving - memory or http requests
     * @return careful Returns a descriptive string for one ofthe two things we could be optimizing over
     */
    public String saveToString() {
        return careful ? "memory" : "number of http requests";
    }

    /**
     * Check what we are saving - memory or number of http requests
     * @return careful Returns <code>true</code> iff we try to save memory
     */
    public boolean save() {
        return careful;
    }

    /**
     * Set to static mode to save memory, and purge the current contents
     */
    public void saveMemory() {
        contents = null;
        careful = true;
    }

    /**
     * Set to static mode to save http requests
     */
    public void saveRequests() {
        careful = false;
    }

}
